
/*
 * sndpipe: A UNIX pipes-based audio stream processor toolkit.
 * Copyright (C) 2011  Bradley Worley
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to:
 *
 *   Free Software Foundation, Inc.
 *   51 Franklin Street, Fifth Floor
 *   Boston, MA  02110-1301, USA.
 */

/* include the sndpipe header. */
#include "sndpipe.h"

/* define the default pipes buffer size. */
#define PIPES_BUF_SIZE  256

/* define the pipes audio format for parsing libsndfile formats. */
struct pipes_format {
  /* declare the format string and integer code. */
  const char *str;
  int fmtid;
};

/* declare the array of accepted pipes formats. */
static struct pipes_format formats[] = {
  { "S8",     SF_FORMAT_AU | SF_FORMAT_PCM_S8 },
  { "U8",     SF_FORMAT_AU | SF_FORMAT_PCM_U8 },
  { "S16_LE", SF_FORMAT_AU | SF_FORMAT_PCM_16 | SF_ENDIAN_LITTLE },
  { "S16_BE", SF_FORMAT_AU | SF_FORMAT_PCM_16 | SF_ENDIAN_BIG },
  { "S24_LE", SF_FORMAT_AU | SF_FORMAT_PCM_24 | SF_ENDIAN_LITTLE },
  { "S24_BE", SF_FORMAT_AU | SF_FORMAT_PCM_24 | SF_ENDIAN_BIG },
  { "S32_LE", SF_FORMAT_AU | SF_FORMAT_PCM_32 | SF_ENDIAN_LITTLE },
  { "S32_BE", SF_FORMAT_AU | SF_FORMAT_PCM_32 | SF_ENDIAN_BIG },
  { SP_UNKNOWN, SP_UNKNOWN }
};

/* pipes_open_input: opens an input pipe to read from standard input.
 *
 * this function attempts to open a pipe input to the application's
 * standard input file descriptor. if successful, a newly allocated
 * pipes structure is returned for reading. otherwise, a NULL pointer
 * is returned.
 */
struct pipes *pipes_open_input (void) {
  /* declare required variables. */
  struct pipes *pi;
  struct stat fdstat;

  /* allocate memory for the pipes structure. */
  pi = (struct pipes *) malloc (sizeof (struct pipes));

  /* ensure allocation of the pipes structure. */
  if (!pi) {
    /* print a failure message and exit. */
    failf ("unable to allocate input pipes struct pointer");
  }

  /* grab the file descriptor for standard input. */
  pi->fd = fileno (stdin);

  /* ensure that we grabbed a valid descriptor. */
  if (pi->fd < 0) {
    /* print a failure message and exit the application. */
    failf ("unable to determine the standard input file descriptor");
  }

  /* query some information from the file descriptor. */
  fstat (pi->fd, &fdstat);

  /* determine whether or not the descriptor is hooked to a pipe. */
  if (S_ISFIFO (fdstat.st_mode)) {
    /* there is a pipe on standard input. try to open it. */
    pi->sfh = sf_open_fd (pi->fd, SFM_READ, &pi->sfi, SP_FALSE);

    /* ensure the pipe was opened successfully. */
    if (!pi->sfh) {
      /* print a failure message and exit. */
      failf ("unable to open input pipe");
    }
  }
  else {
    /* no pipe on standard input. return a NULL pointer. */
    free (pi);
    pi = NULL;
  }

  /* return the newly allocated and opened pipes structure. */
  return pi;
}

/* pipes_open_output: opens an output pipe to write to standard output.
 * @sfi: the sndfile SF_INFO struct used to set up the pipe.
 *
 * this function uses the contents of a sndfile SF_INFO struct to set up
 * a new output pipe for writing audio data to standard output. if the
 * function fails to initialize its pipe, it ends program execution.
 */
struct pipes *pipes_open_output (SF_INFO sfi) {
  /* declare required variables. */
  struct pipes *po;

  /* allocate memory for the pipes structure pointer. */
  po = (struct pipes *) malloc (sizeof (struct pipes));

  /* ensure the pipes struct was allocated. */
  if (!po) {
    /* print a failure message and exit the application. */
    failf ("unable to allocate output pipes struct pointer");
  }

  /* store the pipes stream information in the struct. */
  po->sfi = sfi;
  po->sfi.frames = 0x7FFFFFFF;

  /* grab the file descriptor for our standard output. */
  po->fd = fileno (stdout);

  /* ensure that we grabbed a valid descriptor. */
  if (po->fd < 0) {
    /* print a failure message and exit the application. */
    failf ("unable to determine the standard output file descriptor");
  }

  /* open the file descriptor for reading. */
  FILE *fdh = fdopen (po->fd, "wb");

  /* ensure that we opened the file descriptor. */
  if (!fdh) {
    /* print a failure message and exit the application. */
    failf ("unable to open standard output for writing");
  }

  /* try to open the file descriptor as a sndfile stream. */
  po->sfh = sf_open_fd (po->fd, SFM_WRITE, &po->sfi, SP_FALSE);

  /* ensure the pipe was opened successfully. */
  if (!po->sfh) {
    /* print a failure message and exit. */
    failf ("unable to open output pipe");
  }

  /* return the newly allocated and opened output pipe. */
  return po;
}

/* pipes_close: closes a pipes structure and frees the memory.
 * @p: the pipes struct to close and free.
 *
 * this function closes the file handle associated with a pipes struct
 * and frees all allocated memory. on successful completion, the function
 * returns SP_OK. otherwise it returns SP_FAIL.
 */
int pipes_close (struct pipes *p) {
  /* attempt to close the pipe. */
  if (p && p->sfh && sf_close (p->sfh) != 0)
    return SP_FAIL;

  /* attempt to free the buffer. */
  if (p->buf) free (p->buf);

  /* free the struct pointer. */
  free (p);

  /* return a success status. */
  return SP_OK;
}

/* pipes_write: writes data from a buffer to an output pipe.
 * @p: the output pipes struct to output to.
 * @buf: the buffer to read data from while writing.
 * @frames: the number of frames of data in the buffer.
 *
 * this function writes data in interleaved form to an output pipes struct,
 * where the original data is stored in non-interleaved form. an SP_OK status
 * is returned so long as the number of frames written equals the number of
 * frames specified by the calling function.
 */
int pipes_write (struct pipes *p, double **buf, unsigned long frames) {
  /* declare required variables. */
  unsigned long i, c, count;

  /* ensure proper starting information. */
  if (!p || !p->sfi.channels || !buf || !frames) {
    /* print a failure message and exit the application. */
    failf ("insufficient information passed to pipes_write");
  }

  /* check if we need to allocate our write buffer. */
  if (!p->buf) {
    /* set the buffer size based on the frame and channel counts. */
    p->buf_size = frames * p->sfi.channels;

    /* allocate new memory for the buffer. */
    p->buf = (double *) malloc (sizeof (double) * p->buf_size);

    /* ensure the buffer memory was allocated. */
    if (!p->buf) {
      /* print a failure message and exit the application. */
      failf ("unable to allocate output buffer memory");
    }
  }

  /* loop through the output channels. */
  for (c = 0; c < p->sfi.channels; c++) {
    /* and loop through the frames of each output channel. */
    for (i = 0; i < frames; i++) {
      /* copy the non-interleaved data into the pipes buffer. */
      p->buf[c + i * p->sfi.channels] = buf[c][i];
    }
  }

  /* write the interleaved data out to the pipes stream. */
  count = sf_writef_double (p->sfh, p->buf, frames);
  sf_write_sync (p->sfh);

  /* return a status code based on the number of frames written. */
  return (count == frames ? SP_OK : SP_FAIL);
}

/* pipes_read: reads data into a buffer from an input pipe.
 * @p: the input pipes struct to read from.
 * @buf: a pointer to the buffer to write data into while reading.
 * @frames: the number of frames of data in the buffer.
 *
 * this function reads data in interleaved form into a buffer from an input
 * pipes struct, where the final data is stored in non-interleaved form. an
 * SP_OK status is returned so long as the number of frames read equals the
 * number of frames specified by the calling function.
 */
int pipes_read (struct pipes *p, double ***buf, unsigned long frames) {
  /* declare required variables. */
  unsigned long i, c, count;

  /* ensure proper starting information. */
  if (!p || !p->sfi.channels || !(*buf) || !frames) {
    /* print a failure message and exit the application. */
    failf ("insufficient information passed to pipes_read");
  }

  /* check if we need to allocate our read buffer. */
  if (!p->buf) {
    /* set the buffer size based on the frame and channel counts. */
    p->buf_size = frames * p->sfi.channels;

    /* allocate new memory for the buffer. */
    p->buf = (double *) malloc (sizeof (double) * p->buf_size);

    /* ensure the buffer memory was allocated. */
    if (!p->buf) {
      /* print a failure message and exit the application. */
      failf ("unable to allocate read buffer memory");
    }
  }

  /* read the interleaved data from the pipes stream. */
  count = sf_readf_double (p->sfh, p->buf, frames);

  /* FIXME: pipes_read() returns SP_FAIL in cases where count = 0,
   *  but this is not desirable when the thread should be polling
   *  for input from preceding threads that may be laggy...
   */

  /* loop through the input channels. */
  for (c = 0; c < p->sfi.channels; c++) {
    /* and through the frames of each input channel. */
    for (i = 0; i < frames; i++) {
      /* copy the interleaved data out of the pipes buffer. */
      (*buf)[c][i] = p->buf[c + i * p->sfi.channels];
    }
  }

  /* return a status code based on the number of frames read. */
  return (count == frames ? SP_OK : SP_FAIL);
}

/* pipes_buf_alloc: allocate memory for a buffer based on a pipe.
 * @p: the pipes structure to base the buffer on.
 * @frames: a pointer to the frame count of the buffer.
 *
 * this function allocates a multichannel buffer based on a pipes
 * structure. the frames variable is a pointer to the number of
 * frames desired in each channel. if *frames is set to zero, a
 * default value is used.
 */
double **pipes_buf_alloc (struct pipes *p, unsigned long *frames) {
  /* declare required variables. */
  unsigned long c;
  double **buf;

  /* set *frames to a default value if its set to zero. */
  if (!(*frames))
    *frames = PIPES_BUF_SIZE;

  /* ensure that we have enough pipes information. */
  if (!p || !p->sfi.channels) {
    /* print a failure message and exit the application. */
    failf ("incomplete pipes information passed to pipes_buf_alloc");
  }

  /* allocate memory for the pipes buffer channels. */
  buf = (double **) malloc (sizeof (double *) * p->sfi.channels);

  /* ensure we allocated memory for the channels array. */
  if (!buf) {
    /* print a failure message and exit the application. */
    failf ("unable to allocate buffer channel array memory");
  }

  /* loop through the buffer channels. */
  for (c = 0; c < p->sfi.channels; c++) {
    /* allocate memory for the channel frames. */
    buf[c] = (double *) malloc (sizeof (double) * (*frames));

    /* ensure we allocated memory for the frames. */
    if (!buf[c]) {
      /* print a failure message and exit the application. */
      failf ("unable to allocate memory for buffer frames");
    }
  } 

  /* return the newly allocated buffer. */
  return buf;
}

/* pipes_buf_realloc: reallocate buffer memory for a new frame count.
 * @buf: a pointer to the buffer to reallocate.
 * @p: the pipes struct to use information from.
 * @frames: the new number of frames in the buffer.
 *
 * this function reallocates a pipes buffer variable to contain a new
 * frame count. this is useful when one wishes to enlarge or shrink
 * an already populated buffer.
 */
double **pipes_buf_realloc (double ***buf, struct pipes *p,
                            unsigned long frames) {
  /* declare required variables. */
  unsigned long c;
  double **rebuf;

  /* ensure that we have enough pipes information. */
  if (!p || !p->sfi.channels) {
    /* print a failure message and exit the application. */
    failf ("incomplete pipes information passed to pipes_buf_realloc");
  }

  /* gain a reference to the buffer. */
  rebuf = *buf;

  /* loop through the buffer channels. */
  for (c = 0; c < p->sfi.channels; c++) {
    /* reallocate the memory in the current buffer channel. */
    rebuf[c] = (double *) realloc (rebuf[c], sizeof (double) * frames);

    /* ensure we reallocated memory for the frames. */
    if (!rebuf[c]) {
      /* print a failure message and exit the application. */
      failf ("unable to reallocate memory for new buffer frames");
    }
  }

  /* return the new memory location of the buffer. */
  return rebuf;
}

/* pipes_buf_free: frees an allocated pipes-associated buffer.
 * @p: the pipes struct to use information from.
 * @frames: the number of frames in the buffer.
 * @buf: the pipes buffer to free.
 *
 * this function frees buffers that have been allocated by the corresponding
 * pipes_buf_alloc() function. the arguments and usage are similar to that
 * function.
 */
void pipes_buf_free (struct pipes *p, unsigned long frames, double **buf) {
  /* declare required variables. */
  unsigned long c;

  /* ensure that we have enough pipes information. */
  if (!p || !p->sfi.channels || !buf || !frames) {
    /* print a failure message and exit the application. */
    failf ("incomplete pipes information passed to pipes_buf_free");
  }

  /* loop through the buffer channels, freeing the frames. */
  for (c = 0; c < p->sfi.channels; c++)
    free (buf[c]);

  /* free the buffer channels. */
  free (buf);
}

/* pipes_get_format: parses a string for pipes format information.
 * 
 * this function pulls its information from the format argument in the
 * options parsing array. values are compared to accepted values and,
 * if a match is found, the corresponding SF_INFO format integer is
 * returned. otherwise, a zero value is returned.
 */
int pipes_get_format (void) {
  /* declare required variables. */
  char *fmt;
  int i;

  /* get the format string from the parsed command-line arguments. */
  fmt = opts_get_string (OPTS_S_FORMAT);

  /* ensure the format string is non-null. */
  if (!fmt)
    return SP_UNKNOWN;

  /* loop through the array of known format strings. */
  for (i = 0; formats[i].str; i++) {
    /* see if the currently indexed known format string is a match. */
    if (strcmp (fmt, formats[i].str) == 0)
      return formats[i].fmtid;
  }

  /* return zero. we failed to find a match. */
  return SP_UNKNOWN;
}

/* pipes_get_channels: parses a string for an array of channel numbers.
 * @count: pointer to a counter in which to store the number of channels.
 *
 * this function pulls its information from the channels argument in the
 * options parsing array. provided the function can separate the individual
 * channel numbers from the string information, an array of integers is
 * returned.
 */
unsigned long *pipes_get_channels (unsigned long *count) {
  /* declare required variables. */
  char *chans, *pchans;
  unsigned long i, j, len, C, *chv;

  /* get the channels string from the parsed command-line arguments. */
  chans = opts_get_string (OPTS_S_CHANNELS);
  pchans = chans;

  /* ensure the channels string is non-null. */
  if (!chans)
    return NULL;

  /* get the length of the channels string. */
  len = strlen (chans);

  /* ensure the channels string is positive-length. */
  if (!len)
    return NULL;

  /* loop through each character in the channels string. */
  for (C = 1, i = 0; i < len; i++) {
    /* count the number of commas. */
    if (chans[i] == ',') C++;
  }

  /* allocate memory for the channels array. */
  chv = (unsigned long *) malloc (sizeof (unsigned long) * (C + 1));
  chv[C] = SP_UNKNOWN;

  /* loop through the number of channels. */
  for (i = 0, j = 0; i < C; i++) {
    /* parse the currently indexed number from the string. */
    chv[i] = atol (pchans + j);

    /* loop until we hit the next comma. */
    while (pchans[j] != ',' && j < len) j++;
    j++;
  }

  /* loop through the channels array. */
  for (i = 0; i < C; i++) {
    /* ensure the channel is nonzero. */
    if (!chv[i]) {
      /* scrap the whole op. */
      free (chv);
      return NULL;
    }
  }

  /* set the number of channels. */
  *count = C;

  /* return the channels array. */
  return chv;
}

